
#include "mmap.h"

//#define USING_VMALLOC

#ifdef GMEM_DEBUGB
#define dbg_print(fmt, ...)			\
	do{								\
		printk("GMEM: ");			\
		printk(fmt, ##__VA_ARGS__);	\
		printk("\r\n");				\
	}while(0)
#else
#define dbg_print(fmt, ...)
#endif

static uint8_t *gmem_buffer;

//打开设备
static int gmem_open(struct inode *inode, struct file *file)
{
	return 0;
}

//释放设备
int gmem_release(struct inode *inode, struct file *file)
{
	return 0;
}

//读数据
ssize_t gmem_read(struct file *file, char __user *buffer, size_t size, loff_t *pos)
{
	int ret;
	size_t len = (size > GMEM_SIZE) ? GMEM_SIZE : size;

	ret = copy_to_user(buffer, gmem_buffer, len);
	return len - ret;
}


static int gmem_mmap(struct file *file, struct vm_area_struct *vma)
{
	int result = 0;
#ifndef USING_VMALLOC
	//获取应用层传递的偏移
	unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
	//计算偏移后的内存地址
	unsigned long virt_start = (unsigned long)gmem_buffer + offset;
	//根据内存地址计算物理地址页框号
	unsigned long pfn_start = virt_to_phys((void*)virt_start) >> PAGE_SHIFT;
	//计算欲映射的大小
	unsigned long size = vma->vm_end - vma->vm_start;
	//虚拟地址起始值，可能是用户指定，可能是系统分配
	unsigned long vmstart = vma->vm_start;

	//映射大小必须是PAGE_SIZE的整倍数
	if((size % PAGE_SIZE) != 0)
		size = (size + PAGE_SIZE) / PAGE_SIZE * PAGE_SIZE;;
	//最大不超过全局内存大小
	if(size > GMEM_SIZE)
		size = GMEM_SIZE;

	//增加no cache属性
	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
	//进行内存映射
	dbg_print("phy: 0x%lx, offset: 0x%lx, size: 0x%lx\r\n", pfn_start << PAGE_SHIFT, offset, size);
	result = remap_pfn_range(vma, vmstart, pfn_start , size, vma->vm_page_prot);
	
	return result;
#else
	//获取应用层传递的偏移
	unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
	//计算偏移后的内存地址
	unsigned long virt_start = (unsigned long)gmem_buffer + offset;
	//根据内存地址计算物理地址页框号
	unsigned long pfn_start = (unsigned long)vmalloc_to_pfn((void*)virt_start);
	//计算欲映射的大小
	unsigned long size = vma->vm_end - vma->vm_start;
	//虚拟地址起始值，可能是用户指定，可能是系统分配
	unsigned long vmstart = vma->vm_start;

	//映射大小必须是PAGE_SIZE的整倍数
	if((size % PAGE_SIZE) != 0)
		size = (size + PAGE_SIZE) / PAGE_SIZE;
	//最大不超过全局内存大小
	if(size > GMEM_SIZE)
		size = GMEM_SIZE;
	//增加no cache属性
	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

	dbg_print("phy: 0x%lx, offset: 0x%lx, size: 0x%lx\r\n", pfn_start << PAGE_SHIFT, offset, size);
	while (1) 
	{
		//进行内存映射，每次映射一页，因为vmalloc申请的内存可能不连续
		result = remap_pfn_range(vma, vmstart, pfn_start, PAGE_SIZE, vma->vm_page_prot);
		if (result) 
		{
			dbg_print("remap_pfn_range failed at \r\n");
			break;
		} else
			dbg_print("virt_start = 0x%lx, pfn_start = 0x%lx, vmstart = 0x%lx, size = 0x%lx\r\n", virt_start, pfn_start << PAGE_SHIFT, vmstart, PAGE_SIZE);
		
		//映射完成
		if(size <= PAGE_SIZE)
			break;
		
		//剩余大小递减
		size -= PAGE_SIZE;
		//虚拟地址递加
		vmstart += PAGE_SIZE;
		//全局内存地址递加
		virt_start += PAGE_SIZE;
		//根据内存地址计算物理地址页框号
		pfn_start = vmalloc_to_pfn((void *)virt_start);
	}
	return result;
#endif
}

struct file_operations fops = {
	.open = gmem_open,
	.release = gmem_release,
	.read = gmem_read,
	.mmap = gmem_mmap,
};

struct miscdevice gmem_misc = {
	.minor = GMEM_MINOR,
	.name = GMEM_NAME,
	.fops = &fops,
};

static int __init gmem_init(void)
{
	int err = 0;

	printk("global memory init\r\n");

#ifndef USING_VMALLOC
	gmem_buffer = (uint8_t *)__get_free_pages(GFP_KERNEL, GMEM_ORDER);
#else
	gmem_buffer = vmalloc(GMEM_SIZE);
#endif
	if(gmem_buffer == NULL)
	{
		dbg_print("malloc mem failed\r\n");
		return -ENOMEM;
	}
	dbg_print("virt_start = 0x%lx\r\n", (unsigned long)gmem_buffer);

	err = misc_register(&gmem_misc);
	if(err != 0)
	{
		dbg_print("register misc failed\r\n");
		return err;
	}
	return 0;
}

static void __exit gmem_exit(void)
{
	printk("global memory exit\r\n");

#ifndef USING_VMALLOC
	free_pages((unsigned long)gmem_buffer, GMEM_ORDER);
#else
	vfree(gmem_buffer);
#endif
	misc_deregister(&gmem_misc);
}

module_init(gmem_init);
module_exit(gmem_exit);


MODULE_LICENSE("GPL");
MODULE_AUTHOR("lf");
MODULE_DESCRIPTION("ioctl test");
MODULE_ALIAS("gmem");

